package com.zyso.config;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zyso.pojo.Site;
import com.zyso.vo.SearchResultVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SitesConfig {

    @Autowired
    private LinkedHashMap<Integer, Site> sitesResource;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private SearchResultCacheConfig searchResultCacheConfig;

    //  {siteId, duration}
    private final Cache<Integer, Long> pingCache = Caffeine.newBuilder()
            //  数量上限
            .maximumSize(20)
            //  从最后一次写入后多久就会过期
            .expireAfterWrite(1, TimeUnit.DAYS)
            .build();

    //  {siteId, score}
    private final Cache<Integer, Integer> scoreCache = Caffeine.newBuilder()
            //  数量上限
            .maximumSize(20)
            .build();

    private final int BASE_SCORE = 10;

    /**
     * 获取源
     *
     * @return
     */
    public List<Site> getSites() {
        //  评分
        ConcurrentMap<@NonNull Integer, @NonNull Integer> scoreCacheMap = scoreCache.asMap();
        int scoreCacheSize = scoreCacheMap.size();
        if (scoreCacheSize <= 3) {
            //  延迟
            ConcurrentMap<@NonNull Integer, @NonNull Long> pingCacheMap = pingCache.asMap();
            if (CollectionUtils.isEmpty(pingCacheMap)) {
                //  初始顺序
                Collection<Site> siteCollection = sitesResource.values();
                //  测速
                threadPoolExecutor.execute(() -> this.pingSite(siteCollection));
                return new ArrayList<>(siteCollection);
            }
            //  延迟排序 {duration, siteIds}
            Map<Long, List<Integer>> treeMap = Maps.newTreeMap();
            pingCache.asMap().forEach((k, v) -> treeMap.computeIfAbsent(v, k1 -> Lists.newArrayList()).add(k));
            List<Site> siteList = Lists.newArrayList();
            for (List<Integer> siteIds : treeMap.values()) {
                for (Integer siteId : siteIds) {
                    siteList.add(sitesResource.get(siteId));
                }
            }
            return siteList;
        }
        //  初始化评分
        if (scoreCacheSize != sitesResource.size()) {
            for (Site site : sitesResource.values()) {
                scoreCache.get(site.getId(), key -> BASE_SCORE);
            }
        }
        //  评分越低 源质量越高 {score, siteId}
        Map<Integer, List<Integer>> treeMap = Maps.newTreeMap();
        scoreCacheMap.forEach((k, v) -> treeMap.computeIfAbsent(v, k1 -> Lists.newArrayList()).add(k));
        List<Site> siteList = Lists.newArrayList();
        for (List<Integer> siteIds : treeMap.values()) {
            for (Integer siteId : siteIds) {
                siteList.add(sitesResource.get(siteId));
            }
        }
        log.info("sites:{}", siteList);
        return siteList;
    }

    /**
     * ping site
     *
     * @param siteCollection
     */
    private void pingSite(Collection<Site> siteCollection) {
        //  网络延迟
        for (Site site : siteCollection) {
            try {
                String siteApi = site.getApi();
                String substringBetween = StringUtils.substringBetween(siteApi, "//", "/");
                long startTime = System.currentTimeMillis();
                InetAddress inetAddress = InetAddress.getByName(substringBetween);
                //  超时10秒
                if (inetAddress.isReachable(10000)) {
                    long duration = System.currentTimeMillis() - startTime;
                    Integer siteId = site.getId();
                    pingCache.put(siteId, duration);
                    log.info("{} {} {} ms", siteId, substringBetween, duration);
                } else {
                    log.info("{} UNREACHABLE", substringBetween);
                }
            } catch (IOException e) {
                log.error("", e);
            }
        }
    }

    /**
     * 搜索结果评分
     *
     * @param searchList
     * @param resultList
     */
    public void resultScore(List<Site> searchList, List<SearchResultVO> resultList) {
        if (CollectionUtils.isEmpty(resultList) || searchResultCacheConfig.getSearchResultCacheSize() <= 2) {
            return;
        }
        Map<Integer, SearchResultVO> resultMap = resultList.stream().collect(Collectors.toMap(SearchResultVO::getSiteId, e -> e, (o1, o2) -> o2));
        for (Site site : searchList) {
            Integer siteId = site.getId();
            Integer score = scoreCache.getIfPresent(siteId);
            if (Objects.isNull(score)) {
                score = BASE_SCORE;
            }
            if (resultMap.containsKey(siteId)) {
                //  最小评分 0
                scoreCache.put(siteId, Math.max(0, score - 1));
            } else {
                //  最大评分 20
                scoreCache.put(siteId, Math.min(20, score + 1));
            }
        }
    }

}
